/*
 * @(#)ClassicalAnnealing.java        1.0 2000/06/26
 *
 * This file is part of Xfuzzy 3.0, a design environment for fuzzy logic
 * based systems.
 *
 * (c) 2000 IMSE-CNM. The authors may be contacted by the email address:
 *                    xfuzzy-team@imse.cnm.es
 *
 * Xfuzzy is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * Xfuzzy is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

package xfuzzy.xfsl.model.algorithm;

import xfuzzy.xfsl.model.*;
import xfuzzy.lang.*;
import java.util.Random;
import xfuzzy.xfds.XfdsDataSet;

/**
 * Algortimo de enfriamiento simulado con esquema de enfriamiento cl�sico
 * 
 * @author Francisco Jos� Moreno Velo
 * 
 * @see "Geman, S., Geman, D., Stochastic relaxation, Gibbs distributions 
 * and the Bayesian restoration of images, IEEE Transactions on Pattern 
 * Analysis and Machine Intelligence, Vol. 6, pp. 721-741, 1984."
 *
 */
public class ClassicalAnnealing extends XfslAlgorithm {

	//----------------------------------------------------------------------------//
	//                            MIEMBROS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Temperattura inicial
	 */
	private double T0;
	
	/**
	 * Perturbaci�n inicial
	 */
	private double E0;
	
	/**
	 * N�mero de iteraciones en cada temperatura
	 */
	private int N;

	/**
	 * Temperatura actual
	 */
	private double T;
	
	/**
	 * Relaci�n entre perturbaci�n y temperatura
	 */
	private double ratio;
	
	/**
	 * Iteraci�n actual
	 */
	private int iter;
	
	/**
	 * N�mero de iteraciones con la temperatura actual
	 */
	private int n;
	
	/**
	 * �ltima evaluaci�n
	 */
	private XfslEvaluation last;
	
	/**
	 * Generador de n�meros aleatorios
	 */
	private Random random;

	//----------------------------------------------------------------------------//
	//                                CONSTRUCTOR                                 //
	//----------------------------------------------------------------------------//

	/**
	 * Constructor
	 */
	public ClassicalAnnealing() {
		this.T0 = 0.5;
		this.E0 = 0.1;
		this.N = 10;
		this.random = new Random();
	}

	//----------------------------------------------------------------------------//
	//                             M�TODOS P�BLICOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Devuelve el c�digo de identificaci�n del algoritmo
	 */
	public int getCode() {
		return CLASSICAL_ANNEALING;
	}

	/**
	 * Actualiza los par�metros de configuraci�n del algoritmo
	 */
	public void setParameters(double[] param) throws XflException {
		if(param.length != 3) throw new XflException(26);
		T0 = test(param[0], POSITIVE);
		E0 = test(param[1], POSITIVE);
		N = (int) param[2]; if(N<=0) throw new XflException(26);
	}

	/**
	 * Obtiene los par�metros de configuraci�n del algoritmo
	 */
	public XfslAlgorithmParam[] getParams() {
		XfslAlgorithmParam[] pp = new XfslAlgorithmParam[3];
		pp[0] = new XfslAlgorithmParam(T0, 0.5, POSITIVE, "Initial Temperature");
		pp[1] = new XfslAlgorithmParam(E0, 0.1, POSITIVE, "Initial Perturbation");
		pp[2] = new XfslAlgorithmParam(N, 1, INTEGER, "Iter. per Temp.");
		return pp;
	}

	/**
	 * Ejecuta una iteracion del algoritmo
	 */
	public XfslEvaluation iteration(Specification spec, XfdsDataSet pattern,
			XfslErrorFunction ef) throws XflException {
		if(init) { 
			init = false;
			last = ef.evaluate(spec,pattern,1.0);
			iter = 0;
			n = 0;
			ratio = E0 / T0;
			T = T0;
		} 
		else n++;

		Parameter[] param = spec.getAdjustable();
		if(n==N) { n=0; iter++; cooling(param.length); }
		double[] p = new double[param.length];
		for(int i=0; i<param.length; i++) p[i] = param[i].value;
		perturbation(spec);
		XfslEvaluation eval = ef.evaluate(spec,pattern,last.error);
		boolean metropolis = false;
		double inc = eval.error - last.error;
		if(inc>0 && Math.exp(-inc/T)<random.nextDouble())
			for(int i=0; i<param.length; i++) param[i].value = p[i];
		else metropolis = true;

		if(!metropolis) {
			eval = new XfslEvaluation();
			eval.error = last.error;
			eval.rmse = last.rmse;
			eval.mxae = last.mxae;
			eval.var = 0;
		}

		last = eval;
		return eval;
	}

	//----------------------------------------------------------------------------//
	//                             M�TODOS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Realiza una perturbaci�n de los par�metros del sistema
	 */
	private void perturbation(Specification spec) {
		Type[] type = spec.getTypes();
		for(int i=0; i<type.length; i++) {
			if(!type[i].isAdjustable()) continue;

			Family[] fam = type[i].getFamilies();
			for(int j=0; j<fam.length; j++) {
				if(!fam[j].isAdjustable()) continue;
				Parameter[] pp = fam[j].getParameters();
				double[] val = fam[j].get();
				do {
					for(int k=0; k<pp.length; k++) {
						if(!pp[k].isAdjustable()) continue;
						pp[k].value = val[k] + perturbation();
					}
				} while(!fam[j].test());
			}

			ParamMemFunc[] mf = type[i].getParamMembershipFunctions();
			for(int j=0; j<mf.length; j++) {
				if(!mf[j].isAdjustable()) continue;
				Parameter[] pp = mf[j].getParameters();
				double[] val = mf[j].get();
				do {
					for(int k=0; k<pp.length; k++) {
						if(!pp[k].isAdjustable()) continue;
						pp[k].value = val[k] + perturbation();
					}
				} while(!mf[j].test());
			}
		}
	}

	/**
	 * Actualiza la temperatura siguiendo un esquema de enfriamiento cl�sico
	 */
	private void cooling(int D) {
	    T = T0/(1+Math.log(1+iter));
	}

	/**
	 * Calcula una perturbaci�n siguiendo un esquema de enfriamiento cl�sico
	 */
	private double perturbation() {
	    return ratio*T*random.nextGaussian();
	}
}

